//: [Previous](@previous)

import Foundation

//: ## 可选项(重要)
// 1. 可选项,一般也叫可选类型,它允许将值设置成nil
// 2. 在类型名称后面加个问号 ? ,来定义一个可选项
var name: String? = "Jack"
name = nil
//因为name是可选项,所以才可以赋值成nil

var name1: String
//name1 = nil // 这个没有在类型后面加上问号?,所以不能直接=nil,会报错

var name2: String?
print(name2) //可选项的类型,没有赋值的情况下,会有默认值 nil

//举例需求1: 有一个数组,里面装的是数字.我现在有个方法需要传入下标,获取到这个数组里面的数字
var arrayKX = [1, 2, 33, 44]
func getArrayKxIndex(_ index: Int) -> Int? {
    if index < 0 || index > arrayKX.count {
        return nil
    }
    
    return arrayKX[index]
} //因为这个函数的传参index有可能大于数组的个数,所以需要可以返回nil,这种时候就要用到可选项
getArrayKxIndex(2) // 33
getArrayKxIndex(5) //nil
print(getArrayKxIndex(2)) // Optional(33)\n  有optional证明是可选项
print(getArrayKxIndex(5)) // nil\n
//: ## 强制解包(可选项的补充)
/**
 可选项是对其他类型的一层包装,可以将它理解成一个盒子
  - 如果为nil,它就是一个空盒
  - 如果不为nil,它就是一个装有其他类型的盒子,里面装的是:被包装了类型的数据
 
 如果对值为nil的可选项(空盒子)进行强制解包,将会产生运行时错误
 比如:
 var age: Int?
 age!
 */
//如果要从可选项中取出被包装的数据(将盒子里面的东西取出来使用),需要使用感叹号 ! ,进行强制解包
var age: Int? = 10
print(age) //Optional(10) 这里的age代表的是一个盒子里面装有10,这个实际是一个盒子,不能当做10来直接使用,是会报错的.需要使用就得先把盒子里面的10取出来才能使用
print(age!) //10

let age1: Int = age!
print(age1) // 10
//虽然40行的时候对age强制解包了一次,把盒子里面的10拿出来使用过了,但是这个只是值引用,这个盒子里面的10是可以持续使用的,后续也能再次被用
//: ## 判断可选项是否包含值
let number11 = Int("123") //Int("123")意思是把""里面的123,强制转成Int类型,这种情况是有可能转换失败的,假如""里面是KKK123,就会失败了.所以这种转换系统是会把number11自动设成可选项
print(number11) // Optional(123)
//这种时候就需要做个判断,判断是否会转换成功
if number11 != nil { // 可选项不为nil,证明这个可选项盒子里面有数据,就说明会且是已经转换成功
    print("字符串转换成功:\(number11!)")
} else { //为niu,证明不会转换成功
    print("字符串转换失败")
}
//: ## 可选项绑定 (Optional Binding) (重要)
/**
 可以使用 可选项绑定 来判断可选项是否包含值
  - 如果包含就会自动解包,并且把值赋给一个临时的常量(let)或者变量(var),成功会返回ture(可选项有值并且解包成功),失败会返回false
 */
// 例子1
if let numberOB = Int("123") {
    print("字符串转换整数成功:\(numberOB)")
    // numberOB是强制解包后的Int值
    // numberOB的作用于仅限于这个ture的大括号内(65行-67行)
} else {
    print("字符串转换失败")
} 
//例子2
enum SeasonOB : Int {
    case spring = 1, summer, autumn, winter
}//在SeasonOB枚举中,spring = 1代表spring的原始值是1,根据隐式原始值,1后面的会自动赋值,所以(summer=2, autumn=3, winter=4)
//SeasonOB(rawValue: 6),这个函数是枚举自带的系统函数,取出原始值为6的枚举变量
if let season2 = SeasonOB(rawValue: 6) {
    switch season2 {
    case .spring:
        print("The season2 is spring")
    case .summer:
        print("The season2 is ssummer")
    case .autumn:
        print("The season2 is sautumn")
    case .winter:
        print("The season2 is swinter")
    }
} else {
    print("没有rawValue: 6的枚举变量")
}
//: ## 等价写法(可选项绑定的if后面的判断中,当想使用并且条件的时候,不能用&&,必须用,(逗号)去隔开)
//例子:字符串4 和字符串42 和100的比较
/**
 1. 把"4"转换成Int给first
 2. 把"42"转换成Int给second
 3. 比较4比42小,还比100小
 总结:根据需求,需要同时满足上面3点要求,其实是并列的关系,
 */
//写法1,每一层一个判断,3层判断依次判断,正常的if写法
if let first = Int("4") {
    if let second = Int("42") {
        if first < second && second < 100 {
            print("\(first) < \(second) < 100")
        }
    }
}
//写法2,3个判断同时用逗号隔开,正常的写法应该是直接并列用&&的,但是这里是可选项绑定的语句,不能用&&,所以得用逗号去代替
if let first1 = Int("4"),
    let second1 = Int("42"),
        first1 <  second1 && second1 < 100 {
            print("\(first1) < \(second1) < 100")
}
//: ## while循环中使用可选项绑定
//举例需求: 遍历数组,将遇到的正数都加起来,如果遇到负数或者非数字,停止遍历
var whileArr = ["100", "200", "abc", "-123", "1"]
var index = 0
var sum = 0
while let number = Int(whileArr[index]), number > 0 {//Int(whileArr[index])是把数组的元素拿出来转换,但是把string转换成Int是有可能转换失败的,所以需要使用可选项绑定来操作.成功就转换出值,失败就直接nil.; [, number > 0]这段代码是并列的条件,意思是转换出来的Int要同时满足大于0
    sum += number
    index += 1
}
print(sum)// 100+ 200 =300
//: ## 空合并运算符 ??
/**
 写法:左边一个可选项绑定,中间是两个问号 ??,右边是可选项绑定或者是直接数值
a ?? b
 - a 是可选项
 - b 是可选项 或者 不是可选项
 - b 跟 a 的存储类型必须相同, 比如a是Int("10"),b就必须是Int类型,不管是可选项还是直接的Int
结果:
 - 如果 a 不为 nil, 就返回a
 - 如果 a 为 nil, 就返回b
 - 如果 b 不是可选项,返回a时,会自动对a进行自动解包
结论:
 b的类型是什么,结果就是返回哪种类型.比如b是可选项,结果就是需要返回可选项,b是正常的类型比如Int,结果就是返回自动解包后的a的Int类型
 */
//举例
let a11: Int? = 1
let b11: Int? = 2
let c11 = a11 ?? b11 //a和b都是可选项,a不为nil,所以返回a,结果是Optional(1)

let a22: Int? = nil
let b22: Int? = 2
let c22 = a22 ?? b22 //a和b都是可选项,a为nil,所以返回b,结果是Optional(2)

let a33: Int? = nil
let b33: Int? = nil
let c33 = a33 ?? b33 //a和b都是可选项,a为nil,所以返回b,结果是b的nil

let a44: Int? = 1
let b44: Int = 2
let c44 = a44 ?? b44 //a是可选项,b不是,a不为nil,返回啊.但是因为b不是可选项,所以需要对a进行自动解包,返回1

let a55: Int? = nil
let b55: Int = 2
let c55 = a55 ?? b55 //a和b都是可选项,a为nil,所以返回b,返回是Optional(2),但是因为b不是可选项,所以需要对a进行自动解包变成2

let a66: Int? = nil
let b66: Int = 2
var c66: Int
if let tmp = a66 {
    c66 = tmp
} else {
    c66 = b66
}
//158行-166行的代码 等价与 156行一行的代码,所以这个就是空合并运算符的好处,两个问号就可以达成这个效果(先判断a是不是空,不是空就赋值,是空就赋值b)
//: ## 多个 ?? 一起使用
let a77: Int? = 1
let b77: Int? = 2
let c77 = a77 ?? b77 ?? 3
//这种要从左往右看,先看第一个??(a77 ?? b77),因为b77是可选项,a77不为空,所以返回a的值,b的类型,就是Optional(1);再看第二个??,右边是3,所以返回值需要是int,左边是Optional(1)不为空,所以需要对它强制解包,最终返回:1
print(c77)
//: ## ?? 跟 if let 配合使用
let a8: Int? = nil
let b8: Int? = 2
if let c8 = a8 ?? b8 {
    print(c8)
}//a和b都是可选项,(a8 ?? b8)中a为nil,所以返回b,结果是Optional(8),现在使用可选项绑定if let 去接受Optional(8),c8是不为空,所以会在ture的作用域里面自动解包,输出结果为2
// 类似if a != nil || b != nil,就返回其中一个的需求.可以更加简洁的实现这种需求

if let c9 = a8, let d9 = b8 {
    print(c9,d9)
}
// 类似 if a != nil && b != nil,这种需求,可以直接用上面写法更简洁

//: ## if语句实现登录
//字典中通过key取值(info["username"]),这个写法是直接返回可选项
//举例需求:判断登录成功,并且需要拿到登录名和密码
func login(_ info: [String : String]) {
    if let userName = info["username"], let password = info["password"] {
        //有登录账号且有登录密码
        print("登录成功,账号:\(userName),密码:\(password)")
    }
    
    let userName: String
    if let tmp = info["username"] {
        userName = tmp
    } else {
        print("请输入登录账号")
        return
    }
    
    let password: String
    if let tmp = info["password"] {
        password = tmp
    } else {
        print("请输入登录密码")
        return
    }
    
    print("userName:\(userName), password:\(password)")
}
login(["username" : "jack", "password" : "12313"])
//: ## guard语句(特别好用于某些需要提前退出的需求)
/**
 guard 条件 else {
    // do something....
     退出当前作用域
    // return, break, continue, throw error
 }
 //
 
 1. 当guard语句的条件为false时,就会执行大括号里面的代码
 2. 当guard语句的条件为ture时,就会直接跳过整个guard语句
 重点:所以guard语句特别适用于一些关于提前退出的情况
 */
// 当使用guard语句进行可选项绑定时,绑定的常量(let)/变量(var)也能在外层的作用域使用,和if let是不一样的(比如对上面的登录需求重新写一次)
func loginG (_ info: [String: String]) {
    guard let userName = info["username"] else {
        print("请输入登录名")
        return
    }//假如let userName = info["username"]能成功解包,就是返回ture的意思,就会直接跳过整个guard.但是假如解包失败,就会返回false,就会执行else大括号里面的代码.不管返回ture还是false,let userName的userName外层都是能使用的
    
    guard let password = info["password"] else {
        print("请输入密码")
        return
    }
    
    print("userName:\(userName), password:\(password)")
}
loginG(["username" : "luo", "password" : "11111"])
//guard let语句 和 if let语句 比较,两个login方法可以明显看出,使用if需要先新建一个对象去保存解包成功的数据,步骤是新对象->解包数据->新对象赋值解包后的值,但是guard语句就可以直接创建的值直接解包直接使用
//: ## 隐式解包
/**
 1. 在某些情况下,可选项一旦被设定值之后,就会一直拥有值(就是你能保证这个值一定会有值)
 2. 在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它能确定每次访问的时候都有值
 3. 在这种类型后面加个感叹号!, 就是定义了一个隐式解包的可选项
 */
let num1: Int! = 10
let num2: Int = num1
//这里的num1可以赋值给num2,因为虽然num1还是可选项,但是用了感叹号!隐式解包,在使用的时候会是成为已解包的状态的Int,所以可以直接赋值给同为Int的num2
if num1 != nil {
    print(num1 + 5)// num1虽然是隐式解包了的,但是依旧属于可选项,所以还是可以用于条件判断是否nil,使用的时候却会直接变成已解包的Int
}
if let num3 = num1 {
    print(num3)
}//num1还是属于可选项,虽然是Int后面加的感叹号!,所以还是可以使用可选项绑定if let
//let num4: Int! = nil 这种是不行的,不能对会为nil的值进行隐式解包
//: ## 字符串插值
//可选项在字符串中插值或者直接打印时,编译器会发出警告
var age2: Int? = 10
//print("my age is \(age2)") //会提示这个:String interpolation produces a debug description for an optional value; did you mean to make this explicit?
//3种消除警告的方法,一般使用前两种
print("my age is \(age2!)") //隐式解包
print("my age is \(age2 ?? 0)") //合并运算符
print("my age is \(String(describing: age2))") //对可选项的描述,不会解包,所以没用
//: ## 多重可选项
var num11: Int? = 10
var num22: Int?? = num11
var num33: Int?? = 10
// num11:装有Int10的盒子
// num22:装有num11这个盒子的盒子,所以是2个问号??,2个问号,代表是2个盒子
// num33:也是看成一个盒子,只是经过2层可选项解包后,结果是10.2个问号,代表是2个盒子
print(num22 == num33)
//22和33都是2个盒子,因为22=11,而11是10,所以其实22最终也是10,所以22和33是一样的

//多重可选项使用nil的时候,有陷阱,和上面就不一样了
var num44: Int? = nil
var num55: Int?? = num44
var num66: Int?? = nil
// num44:一个为空的盒子
// num55:一个盒子,里面装着一个为空的盒子,一共是2个盒子
// num66:一个为空的盒子,虽然是两个问号,理论上应该是2个盒子,但是等式是=nil,所以是里面全空
print(num55 == num66)

(num55 ?? 1) ?? 2 //2  (num55 ?? 1)中,因为??右边是1(非可选项),所以左边需要解包.num55解包后是num44,num44是nil,所以(num55 ?? 1) = nil. 就变成了 nil ?? 2,左边为空,返回右边,所以结果是2
(num66 ?? 1) ?? 2 //1  (num66 ?? 1)中,因为??右边是1(非可选项),所以左边需要解包,num66解包后是nil,所以返回右边1,变成了 1 ?? 2,左边ture,结果是1

//可以使用lldb指令(不能在playground中使用),frame variable -R 或者 fr v -R 查看区别 用于查看结构
//: ## 可选项的本质
// 可选项的本质是 enum 类型, 其实就是包装过的泛型
var age5: Int? = 10
age5 = 20
age5 = nil
//age5和age6其实本质是一样的
var age6: Optional<Int> = Optional<Int>.some(10)
age6 = .some(20)
age6 = .none //nil
var age7: Optional = .some(10) //右边(10)直接标明了Int类型, 所以左边声明的泛型就可以不写明类型
var age8 = Optional.some(10)
var age9 = Optional(10)
//👇🏻3行为空值的效果一样,Optional后面的<Int>不能省略,因为泛型必须在初始化的时候明确需要初始化的类型,哪怕没有用上也要所有可能都要明明白白的
var age10: Int? = nil
var age11 = Optional<Int>.none
var age12: Optional<Int> = .none
//:## 用Switch判断可选类型是否有值
var age13: Int? = 10 //可选类型
//解包使用的方式1
switch age13 {
case let v?: //这个?就是解包的意思
    print(v) //这里的v直接就是解包后的具体值,假如是空值就到下面一行
case nil:
    print("nil")
}

//解包使用的方式2
if let v = age13 {
    print(v)
} else {
    print("nil")
}

// 关于两个问号 ??  👇🏻3种写法一样效果 其实就是每个问号相当于一个可选项, 一个可选项里面保证另一个可选项
var age14: Int?? = 10
var age15: Optional<Optional> = 10
var age16 = Optional.some(Optional.some(10))






//: [Next](@next)
